var Emitter = require('emitter');
var walkJson = require('./walk_json');

function ArrayMgr(items) {
  Emitter.call(this);
  
  // _items stores the real items
  if (items && Array.isArray(items)) {
    this._items = items;
  } else {
    this._items = [];
  }
}

ArrayMgr.prototype = new Emitter();

ArrayMgr.prototype.length = function() {
  return this._items.length;
};

ArrayMgr.prototype.add = function(item, silent) {
  this._items.push(item);
  
  var self = this;
  if (typeof item.on === 'function') {
    item.on('size-changed', function() {
      if (self.find(item) !== -1) {
        self.emit('size-changed');
      }
    });
  }
  
  // Emit events about this function call, if not silent
  if (!silent) {
    this.emit('add', {
      item: item,
      index: this.length() - 1
    });
  }
};

ArrayMgr.prototype.prepend = function(item, slient) {
  this._items.unshift(item);
  
  var self = this;
  if (typeof item.on === 'function') {
    item.on('size-changed', function() {
      if (self.find(item) !== -1) {
        self.emit('size-changed');
      }
    });
  }
  
  if (!slient) {
    this.emit('prepend', {
      item: item,
      index: 0
    });
  }
};

ArrayMgr.prototype.rm = function(item, silent) {
  var index = this.find(item);
  if (index === -1) {
    return;
  }
  this._items.splice(index, 1);
  if (!silent) {
    this.emit('rm', {
      item: item,
      index: index
    });
  }
};

// Move item to a new index in the array
// If item is not found in the array, do nothing
ArrayMgr.prototype.move = function(item, nindex, silent) {
  var index = this.find(item);
  if (index === -1 || index === nindex) {
    return;
  }
  this._items.splice(index, 1);
  var r = this._items.splice(nindex);
  this._items.push(item);
  this._items = this._items.concat(r);
  if (!silent) {
    this.emit('moveto', {
      item: item,
      index: nindex
    });
  }
};

/* Why implement this function instead of using native forEach?
 * 1) I need to be able to break the loop
 * 2) Different browsers have different implementations and differs in the iterate order
 */
ArrayMgr.prototype.iterate = function(fn) {
  if (!fn) {
    return;
  }
  for (var i = 0, len = this.length(); i < len; i++) {
    var result = fn(this._items[i], i);
    if (result) {
      break;
    }
  }
};

ArrayMgr.prototype.backIterate = function(fn) {
  if (!fn) {
    return;
  }
  var i = this.length();
  while (i--) {
    var result = fn(this._items[i], i);
    if (result) {
      break;
    }
  }
};

// Return the index of the item in the array
// Returns -1 if not found
ArrayMgr.prototype.find = function(item) {
  var index = -1;
  this.iterate(function(_item, _index) {
    if (item === _item) {
      index = _index;
      return true;
    }
  });
  return index;
};

ArrayMgr.prototype.query = function(qs) {
  var self = this;
  var result;
  this.iterate(function(item) {
    var hit = true;
    walkJson(qs, function(key, value, paths) {
      var obj = item;
      while ( paths.length !== 0) {
        var p = paths.shift();
        if (obj[p] == null) {
          hit = false;
          return true;
        } else {
          obj = obj[p];
        }
      }
      if (obj[key] == null) {
        hit = false;
        return true;
      }
      if (value instanceof RegExp) {
        if (!value.test(obj[key])) {
          hit = false;
          return true;
        }
      } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
        if (value !== obj[key]) {
          hit = false;
          return true;
        }
      }
    });
    if (hit) {
      result = item;
      return true;
    }
  });
  return result;
};

// Empty the array
ArrayMgr.prototype.empty = function() {
  for (var i = 0, len = this._items.length; i < len; i++) {
    this._items[i] = null;
  }
  this._items = [];
};

// Get item by index
ArrayMgr.prototype.get = function(index) {
  return this._items[index];
};

// Set item by index
ArrayMgr.prototype.set = function(index, item) {
  if (this.length() > index) {
    this._items[index] = item;
  }
};

ArrayMgr.prototype.rmByIndex = function(index, silent) {
  if (!this._items[index]) {
    return;
  }
  this._items.splice(index, 1);
  if (!silent) {
    this.emit('rm');
  }
};

ArrayMgr.prototype.dispose = function() {
  this.empty();
  this._items = null;
};

module.exports = ArrayMgr;
